-- Radical by Basil Voronkov
-- Module 'dialog'
---------------------------------------------
-- This module implements dialog engine. Dialogs
-- are stored in XML files which processed using
-- module 'xml'.
require "sprites"
require "lib/xml"
require "lib/table"
require "lib/core"
require "lib/engine"
require "lib/const"

dia = {
  
}

GF_NEXT = img(const.path.ui.."next.png")..txtnb(" ")

function dia.init()
  _G["a1"] = xact ("dia.a1", code [[return dia.answer(1)]]);
  _G["a2"] = xact ("dia.a2", code [[return dia.answer(2)]]);
  _G["a3"] = xact ("dia.a3", code [[return dia.answer(3)]]);
  _G["a4"] = xact ("dia.a4", code [[return dia.answer(4)]]);
  _G["a5"] = xact ("dia.a5", code [[return dia.answer(5)]]);
  _G["a6"] = xact ("dia.a6", code [[return dia.answer(6)]]);
  _G["a7"] = xact ("dia.a7", code [[return dia.answer(7)]]);
  _G["a8"] = xact ("dia.a8", code [[return dia.answer(8)]]);
  _G["a9"] = xact ("dia.a9", code [[return dia.answer(9)]]);
  _G["a10"] = xact ("dia.a10", code [[return dia.answer(10)]]);
  _G["a11"] = xact ("dia.a11", code [[return dia.answer(11)]]);
  _G["a12"] = xact ("dia.a12", code [[return dia.answer(12)]]);
  _G["a13"] = xact ("dia.a13", code [[return dia.answer(13)]]);
  _G["a14"] = xact ("dia.a14", code [[return dia.answer(14)]]);
  _G["a15"] = xact ("dia.a15", code [[return dia.answer(15)]]);
  _G["a16"] = xact ("dia.a16", code [[return dia.answer(16)]]);
  _G["a17"] = xact ("dia.a17", code [[return dia.answer(17)]]);
  _G["a18"] = xact ("dia.a18", code [[return dia.answer(18)]]);
  _G["a19"] = xact ("dia.a19", code [[return dia.answer(19)]]);
  _G["a20"] = xact ("dia.a20", code [[return dia.answer(20)]]);
  _G["a21"] = xact ("dia.a21", code [[return dia.answer(21)]]);
  _G["a22"] = xact ("dia.a22", code [[return dia.answer(22)]]);
  _G["a23"] = xact ("dia.a23", code [[return dia.answer(23)]]);
  _G["a24"] = xact ("dia.a24", code [[return dia.answer(24)]]);
  _G["a25"] = xact ("dia.a25", code [[return dia.answer(25)]]);
  _G["a26"] = xact ("dia.a26", code [[return dia.answer(26)]]);
  _G["a27"] = xact ("dia.a27", code [[return dia.answer(27)]]);
  _G["a28"] = xact ("dia.a28", code [[return dia.answer(28)]]);
  _G["a29"] = xact ("dia.a29", code [[return dia.answer(29)]]);
  _G["a30"] = xact ("dia.a30", code [[return dia.answer(30)]]);
  _G["aback"] = xact("dia.aback", code[[here().lastdsc=dia.gen_dsc(here().model);here()._cid=0;return dia.returnback()]]);
end

function dia.walkback()
  ref(here()._dlgfrom).tmpdsc=nil;
  walk(here()._dlgfrom);
end

function dia.read_lines(dlg, o, par)
  local arr = {};

  for i,v in ipairs(o) do 
    dlg.idx = dlg.idx + 1;
    local pit = {
       id       = dlg.idx
      ,key      = v.xarg.key
      ,next     = v.xarg.next
      ,a        = v.xarg.a
      ,aa       = v.xarg.aa
      ,b        = v.xarg.b
      ,act      = v.xarg.action
      ,pause    = v.xarg.pause
      ,snd      = v.xarg.sound
      ,cond     = v.xarg.condition
      ,walk     = v.xarg.walk
      ,take     = v.xarg.take
      ,drop     = v.xarg.drop
      ,levelup  = v.xarg.levelup
      ,goto     = v.xarg.goto
      ,noline   = v.xarg.noline
      ,alwaysOn = v.xarg.alwaysOn
      ,mus      = v.xarg.mus
      ,children = nil
      ,parent   = par
    }
    
    if dlg.alwaysOn == "true" then
      pit.alwaysOn = "true";
    end
    
    table.insert(arr, pit);
    local c = dia.read_lines(dlg, v, pit);
    
    if v.xarg.next ~= nil then
      table.insert(dlg.keyed, pit);
    end
    
    if table.len(c) ~= 0 then
      pit.children = c;
    end
  end
  
  return arr;
end

function dia.find_root(tab)
  if tab.id == 0 then
    return tab;
  end
  local t = tab.parent;
  local s = here();
  
  while true do
    if t.id == 0 then
      return t;
    elseif table.len(t.children) > 1 then
      local c = 0;
    
      for i,v in ipairs(t.children) do
        if v.alwaysOn == "true" or (not s["_a"..tostring(v.id)] and (v.aa ~= nil or t.id == 0) 
          and (v.cond == nil or apply(v.cond))) then
          c = c + 1;
        end
      end
      
      if c > 0 then
        return t;
      else
        t = t.parent;
      end
    else
      t = t.parent;
    end
  end
end

function dia.find_bykey(o, key)
  for i,v in ipairs(o) do
    if v.key == key then
      return v;
    end
    if v.children ~= nil then
      local pc = dia.find_bykey(v.children,key);
      if pc ~= nil then
        return pc;
      end
    end
  end
  return nil;
end

function dia.find_tab(o, id)
  if id == nil or id == 0 then
    return o;
  end
  if o.id == 0 then
    o = o.children;
  end
  for i,v in ipairs(o) do
    if v.id == id then
      return v;
    end
    if v.children ~= nil then
      local pc = dia.find_tab(v.children,id);
      if pc ~= nil then
        return pc;
      end
    end
  end
  return nil;
end
  
function dia.gen_dsc(tab)
  local dsc = nil;
  local s = here();
  if tab.b ~= nil then
    dsc = tab.b:expand();
  else
    dsc = rcall(s.defdsc, s);
    if dsc ~= nil then
      dsc = dsc:expand();
    end
  end
  if dsc == nil then
    dsc = "";
  end
  if dsc ~= "" then
    dsc = dsc.."^^";
  end
  
  local cl = tab.children;
  local retu = false;
  local curid = tab.id;  
  if cl == nil then
    local pt = dia.find_root(tab);
    curid = pt.id;
    cl = pt.children;
    s._cid = pt.id;
    retu = true;
  end
  
  if cl == nil then
    cl = {};
  end
  
  local cc = 0;
  for i,v in ipairs(cl) do
    if not s["_a"..tostring(v.id)] and (v.cond == nil or apply(v.cond)) 
      and (not retu or v.aa ~= nil or v.alwaysOn or curid == 0) then
      local a = v.a;
      if retu and v.aa ~= nil and v.aa ~= "a" then
        a = v.aa;
      end
      local sep = "^";
      if cc == 0 or v.noline == "true" then
        sep = "";
      end
      if a == "^" then
        dsc = dsc.."^";
      else
        dsc = dsc..sep.."{a"..tostring(i).."|"..a:expand().."}";
      end
      cc = cc + 1;
    end
  end
  
  if curid == 0 and s.talkend ~= nil then
    if cc > 0 then
      dsc = dsc.."^^{aback|[Завершить]}";
    else
      dsc = dsc.."{aback|[Поговорить позже]}";
    end
  elseif cc == 0 then
    dsc = dsc.."{aback|[Поговорить позже]}";
  end
  
  return dsc;
end

function dia.answer(i)
  local s = here();
  local oldtab = dia.find_tab(s.model, s._cid);
  local tab = oldtab.children[i];
  local nosnd = false;
  if tab.snd ~= nil then
    play_sound(tab.snd,nil,3);
    nosnd = true;
  end
  if tab.act ~= nil then
    exec(tab.act);
  end
  if tab.alwaysOn ~= "true" then
    s["_a"..tostring(tab.id)] = true;
  end
  if tab.walk ~= nil then
    s._cid = 0;
    s._tmpdsc = nil;
    if tab.walk == "back" then
      dia.walkback();
    else
      ref(tab.walk).tmpdsc=nil;
      walk(tab.walk);
    end
    return true;
  end
  if tab.levelup == "true" then
    tab = oldtab.parent;
  end
  if tab.goto ~= nil then
    if tab.goto == "root" then
      tab = s.model;
    else
      tab = dia.find_bykey(s.model.children, tab.goto);
    end
  end
  s._pause = tab.pause;
  s._cid = tab.id;
  s._tmpdsc = dia.gen_dsc(tab);
  s.oldlink = nil;
  if not nosnd and not s.silent then
    set_sound("theme/click.wav",3);
  end
  s:draw();
  return true;
end

function dia.returnback()
  local s = here();
  s._cid = 0;
  s._tmpdsc = nil;
  if is_function(s.talkend) then
    s:talkend();
  elseif s.talkend == "back" then
    walkback();
  elseif s.talkend ~= nil then
    walk(s.talkend);
  else
    dia.walkback();
  end
  return true;
end

function tos(tab)
  local res = "";
  for k,v in pairs(tab) do
    res = res.."[[" ..
      "id="..v.id;
    if v.children ~= nil then
      res=res..",children="..tos(v.children);
    end
    res=res.."]];";
  end
  return res;
end

function dialog(v)
  local enter = v.enter;
  local r = {
     _state     = 0
    ,bounds     = v.bounds
    ,tiles      = v.tiles
    ,tile       = v.tile
    ,fps        = nvl(v.fps,20)
    ,alwaysOn   = false
    ,idx        = 0
    ,keyed      = {}
    ,npcnam     = v.nam
    ,npcpic     = v.img
    ,hideinv    = true
    ,forcedsc   = true
    ,fading     = v.fading
    ,vexit      = v.exit
    ,talkend    = v.talkend
    ,defdsc     = v.dsc
    ,lastdsc    = nil
    ,dlg        = v.dlg
    ,enter      = v.enter
    ,noselitem  = true
    ,label      = v.label
    ,silent     = v.silent
    ,enter      = function(s,f)
                    if enter ~= nil then
                      enter(s,f);
                    end
                    if deref(s) ~= deref(f) then
                      s._dlgfrom = deref(f);
                    end
                  end
    ,ondraw     = function(s)
                    local txt = s:dsc_dia();
                    if txt ~= nil then
                      local bounds = nvl(rcall(s.bounds,s),{});
                      local x,y,w = nvl(bounds[1],20),nvl(bounds[2],40),nvl(bounds[3],800);
                      drawing.render_multiline_text(s, s.pic, txt, const.font, 14, const.textcolor, x, y, w, true, 20);
                    end
                  end
    ,dsc_dia    = function(s) 
                    if s._tmpdsc ~= nil then
                      return s._tmpdsc;
                    else
                      return dia.gen_dsc(s.model);
                    end
                  end
    ,obj        = { "a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "a10", 
                    "a11", "a12", "a13", "a14", "a15", "a16", "a17", "a18", "a19", "a20", 
                    "a21", "a22", "a23", "a24", "a25", "a26", "a27", "a28", "a29", "a30", "aback"
                  }
  };

  copy_attr(v, r); 
  r.dsc = nil;
  local dlg = v.dlg;
  local root = { id = 0 };
  local retval = engine.gameroom(r);
  local xmldoc = nil;
  
  if dlg ~= nil then
    xmldoc = xml.read(dlg);
    
    if not is_table(xmldoc) then
      error("Invalid encoding: "..dlg);
    end
    if xmldoc[1].xarg == nil then
      error("Invalid dialog file: "..dlg);
    end
  elseif v.src ~= nil then
    xmldoc = xml.collect("mem", "<dialog>"..v.src.."</dialog>");
  end
  
  if xmldoc ~= nil then
    retval.alwaysOn = xmldoc[1].xarg.alwaysOn;
    root.children = dia.read_lines(retval, xmldoc[1], root);
    
    for i,v in ipairs(retval.keyed) do
      local kt = dia.find_bykey(root.children, v.next);
      if kt == nil then
        error("Unable to find table with key: "..v.next);
      end
      v.children = kt.children;
    end
  end
    
  retval.model = root;
  return retval;
end